﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Migrator.Packets;
using Migrator.Util;
using System.Diagnostics;
using Migrator.Core;
using Migrator.Events;
using System.Reflection;
using Migrator.Services;

namespace Migrator.Entities
{
    /// <summary>
    /// Represents a network node, whether it's a router or a simple
    /// workstation.
    /// </summary>
    public abstract class Node
    {
        #region Constructors
        /// <summary>
        /// Creates a new Node
        /// </summary>
        /// <param name="defaultRouter">The default router for this node</param>
        public Node(MACAddress macAddress, IPAddress ipAddress, Router defaultRouter, PointF location)
        {
            this.MACAddress = macAddress;
            this.IPAddress = ipAddress;
            this.DefaultRouter = defaultRouter;
            this.Location = location;
            this.Radius = Migrator.Properties.Settings.Default.DefaultRadius;
        }
        #endregion

        #region Public methods

        /// <summary>
        /// Performs a single step of the simulation.
        /// </summary>
        public virtual void Update()
        {
            //Console.WriteLine(this.ToString() + " starting update...");

            ProcessReceivedPackets();

            // Update each service
            foreach (var service in this.services)
            {
                if (service.Enabled)
                    service.Update();
            }

            //Console.WriteLine(this.ToString() + " starting update...");
        }

        /// <summary>
        /// Enqueues a new message in the inbox
        /// </summary>
        /// <param name="message">The message received</param>
        public virtual void Enqueue(IPPacket message)
        {
            // Add the message to the inbox, to be handled in
            // the next iteration (thus the + 1)
            int key = Simulator.Instance.Clock + 1;
            if (this.inbox.ContainsKey(key))
            {
                this.inbox[key].Add(message);
            }
            else
            {
                this.inbox.Add(key, new List<IPPacket> { message });
            }
        }


        /// <summary>
        /// Sends a message to the destination.
        /// </summary>
        /// <param name="message">The message to send</param>
        public virtual void Send(IPPacket packet)
        {
            // Broadcast messages are sent with the help of the Internet,
            // because only the Internet knows where every node is
            if (packet.DestinationIP == IPAddress.BroadcastAddress)
            {
                // Always add this node to the list of Hops
                packet.Hops.Add(this);

                Debug.WriteLine("<<< " + this.ToString() + " sending " + packet + " to " + Simulator.Instance.Internet);
                Simulator.Instance.Internet.Enqueue(packet);
            }
            else
            {
                this.SendLinkLayer(packet, this.DefaultRouter.MACAddress);
            }
        }

        /// <summary>
        /// Sends a packet directly through link-layer mechanisms, 
        /// without IP routing
        /// </summary>
        /// <remarks>
        /// Checks if the destination's node is in wireless range.
        /// Also, ignores completely the destination's IP address.
        /// </remarks>
        /// <param name="packet">The packet to send</param>
        /// <param name="destination">Destination's MAC address</param>
        public virtual void SendLinkLayer(IPPacket packet, MACAddress destinationAddress)
        {
            // If some listener canceled the event, abort
            if (this.OnBeforePacketSent(packet))
                return;

            // Always add this node to the list of Hops
            if (!(this is Internet))
                packet.Hops.Add(this);

            Node destination = Simulator.Instance.Internet[destinationAddress];

            if (!(this is Internet))
                Debug.WriteLine("<<< " + this.ToString() + " sending " + packet + " to " + destination);

            if (this.IsInRange(destination))
            {
                destination.Enqueue(packet);
                OnPacketSent(packet, destination);
            }
            else
            {
                Debug.WriteLine("*** " + this.ToString() + " send failed: " + destination + " is out of range");
            }
        }


        /// <summary>
        /// Returns the distance to some other node
        /// </summary>
        /// <param name="other">The other node</param>
        /// <returns>The distance calculated</returns>
        public virtual double DistanceTo(Node other)
        {
            return this.Location.DistanceTo(other.Location);
        }

        /// <summary>
        /// Checks if the given node is in range
        /// </summary>
        /// <param name="other">The node to check</param>
        /// <returns>true if the node is in range, false otherwise</returns>
        public bool IsInRange(Node other)
        {
            return this.DistanceTo(other) <= this.Radius || other is Internet;
        }

        public override string ToString()
        {
            return this.GetType().Name + "[" + this.IPAddress + "]";
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Node's current IPv4 address
        /// </summary>
        public IPAddress IPAddress
        {
            get { return this.ipAddress; }
            protected internal set
            {
                IPAddress oldIP = this.IPAddress;
                this.ipAddress = value;
                this.OnIPAddressChanged(oldIP);
            }
        }

        /// <summary>
        /// Node's MAC address
        /// </summary>
        public MACAddress MACAddress
        {
            get;
            protected set;
        }

        /// <summary>
        /// Node's default router
        /// </summary>
        public Router DefaultRouter
        {
            get { return this.defaultRouter; }
            protected internal set
            {
                if (value != null && this.defaultRouter != value)
                {
                    if (this.defaultRouter != null)
                    {
                        this.DefaultRouter.DeregisterNode(this);
                    }

                    Router oldRouter = this.DefaultRouter;
                    this.defaultRouter = value;
                    this.DefaultRouter.RegisterNode(this);
                    this.OnDefaultRouterChanged(oldRouter);
                }
            }
        }

        /// <summary>
        /// Node's location
        /// </summary>
        public PointF Location
        {
            get { return this.location; }
            set
            {
                PointF oldLocation = this.location;
                this.location = value;
                this.OnLocationChanged(oldLocation);
            }
        }

        /// <summary>
        /// Node's wireless device radius coverage
        /// </summary>
        public int Radius
        {
            get { return this.radius; }
            set
            {
                int oldRadius = this.radius;
                this.radius = value;
                this.OnRadiusChanged(oldRadius);
            }
        }
        
        #endregion

        #region Public events

        /// <summary>
        /// Event raised when a message is sent
        /// </summary>
        public event PacketSentEventHandler PacketSent;
        public delegate void PacketSentEventHandler(Node source, PacketSentEventArgs eventArgs);

        /// <summary>
        /// Event raised when a message is going to be sent
        /// It allows the listener to cancel the send
        /// </summary>
        public event BeforePacketSentEventHandler BeforePacketSent;
        public delegate void BeforePacketSentEventHandler(Node source, BeforePacketSentEventArgs eventArgs);

        /// <summary>
        /// Event raised when a message is received
        /// </summary>
        public event PacketReceivedEventHandler PacketReceived;
        public delegate void PacketReceivedEventHandler(Node source, IPPacket packet);

        /// <summary>
        /// Event raised when the IP address changes
        /// </summary>
        public event IPAddressChangedEventHandler IPAddressChanged;
        public delegate void IPAddressChangedEventHandler(Node source, IPAddress oldIP);

        /// <summary>
        /// Event raised when the default router changes
        /// </summary>
        public event DefaultRouterChangedEventHandler DefaultRouterChanged;
        public delegate void DefaultRouterChangedEventHandler(Node source, Router oldRouter);

        /// <summary>
        /// Event raised when the location changes
        /// </summary>
        public event LocationChangedEventHandler LocationChanged;
        public delegate void LocationChangedEventHandler(Node source, PointF oldLocation);

        /// <summary>
        /// Event raised when the radius changes
        /// </summary>
        public event RadiusChangedEventHandler RadiusChanged;
        public delegate void RadiusChangedEventHandler(Node source, int oldRadius);

	    #endregion

        #region Raise events

        protected void OnPacketSent(IPPacket packet, Node destination)
        {
            if (this.PacketSent != null)
                this.PacketSent(packet.Hops.Last(), new PacketSentEventArgs(packet, destination));
        }

        protected bool OnBeforePacketSent(IPPacket packet)
        {
            if (this.BeforePacketSent != null)
            {
                var args = new BeforePacketSentEventArgs(packet);
                Node source = (packet.Hops.Count == 0 ? this : packet.Hops.Last());
                this.BeforePacketSent(source, args);
                return args.Cancel;
            }

            return false;
        }

        protected void OnPacketReceived(IPPacket packet)
        {
            if (this.PacketReceived != null)
                this.PacketReceived(this, packet);
        }

        protected void OnIPAddressChanged(IPAddress oldIP)
        {
            if (this.IPAddressChanged != null)
                this.IPAddressChanged(this, oldIP);
        }

        protected void OnDefaultRouterChanged(Router oldRouter)
        {
            if (this.DefaultRouterChanged != null)
                this.DefaultRouterChanged(this, oldRouter);
        }

        protected void OnLocationChanged(PointF oldLocation)
        {
            if (this.LocationChanged != null)
                this.LocationChanged(this, oldLocation);
        }

        protected void OnRadiusChanged(int oldRadius)
        {
            if (this.RadiusChanged != null)
                this.RadiusChanged(this, oldRadius);
        }

        #endregion

        #region Internal methods

        /// <summary>
        /// Processes received packets.
        /// </summary>
        protected virtual void ProcessReceivedPackets()
        {
            // Check if it's time to handle the packet
            while (inbox.Count > 0 && inbox.First().Key <= Simulator.Instance.Clock)
            {
                var pair = inbox.First();
                inbox.RemoveAt(0);

                foreach (var packet in pair.Value)
                {
                    OnPacketReceived(packet);
                    this.ProcessPacket(packet);
                };
            }
        }

        /// <summary>
        /// Process incoming packet.
        /// By default, invokes the packet's handler.
        /// </summary>
        /// <param name="message">The packet received</param>
        protected virtual void ProcessPacket(IPPacket packet)
        {
            bool forMe = packet.DestinationIP == this.IPAddress || packet.DestinationIP == IPAddress.BroadcastAddress;

            // Forward 
            foreach (var service in this.services)
            {
                if (service.Enabled && forMe && service.Filter(packet))
                    service.Handle(packet);
            }
        }

        #endregion

        #region Internal data

        /// <summary>
        /// Node's inbox queue, with messages waiting to be processed.
        /// The value of each element is the message received. On the other hand,
        /// the key is the time when the message should be handled.
        /// </summary>
        /// <seealso cref="Enqueue"/>
        protected SortedList<int, List<IPPacket>> inbox = new SortedList<int, List<IPPacket>>();

        /// <summary>
        /// Services being used by this node
        /// </summary>
        protected List<IService> services = new List<IService>();

        protected IPAddress ipAddress;
        protected Router defaultRouter;
        protected PointF location;
        protected int radius;

        #endregion

        public IService GetService(Migrator.Services.Services service)
        {
            return this.services.First(s => s.GetType().Name == service.ToString());
        }
    }
}
